/**
 * InvientEngine - Experimental physics and graphics library
 * Copyright (C) 2011  Shane Transue
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see http://www.gnu.org/licenses/gpl.html
 *
 * No line in this file should exceed 80 characters in width.
 */

package com.invient.math;

/**
 * The Matrix4f class represents a 4x4 Matrix that holds float values. The
 * design of this class is focused on performance and therefore the 
 * implementations of the methods in this class focus on performance rather
 * than the other various implementations that may provide more general
 * functionality.
 */
public class Matrix4f extends Matrixf
{
    private static final int MATRIX_SIZE = 16;
    private static final int MATRIX_ROW_SIZE = 4;
    private static final int MATRIX_COL_SIZE = 4;
    
    /**
     * Creates a new 4x4 Matrix. The default Matrix has the identity loaded
     * to reduce code inflation for a newly created Matrix.
     */
    public Matrix4f() {
        this.data = new float[MATRIX_SIZE];
        this.identity();
    }
    
    /**
     * Loads the identity Matrix into this matrix.
     */
    public void identity() {
        super.identity(MATRIX_SIZE, MATRIX_ROW_SIZE);
    }
    
    /**
     * This method sets the value at the provided indicies to the provided
     * value.
     *
     * @param i The row index of the element to set.
     * @param j The column index of the element to set.
     * @param data The data that will be replace the old value at the position
     * in the Matrix provided.
     */
    public void set(int i, int j, float data) {
        int index = (i * MATRIX_ROW_SIZE + j);
        this.data[index] = data;
    }
    
    /**
     * This method returns the value at the provided indices.
     *
     * @param i The row index of the element to retrieve.
     * @param j The column index of the element to retrieve.
     * @return Returns the value at the provided indicies.
     */
    public float get(int i, int j) {
        int index = (i * MATRIX_ROW_SIZE + j);
        return this.data[index];
    }
    
    /**
     * This method sets the values of an entire column in this Matrix.
     *
     * @param index The index of the column to replace.
     * @param column The column values that will replace the existing column.
     */
    public void setColumn(int index, Vector4f column) {
        int column_index = 0;
        
        for ( int i = index; i < MATRIX_SIZE; i += MATRIX_ROW_SIZE ) {
            this.data[i] = column.get(column_index);
            column_index++;
        }
    }
    
    /**
     * This method sets the values of an entire row in this Matrix.
     *
     * @param index The index of the row to replace.
     * @param row The row values that will replace the existing row.
     */
    public void setRow(int index, Vector4f row) {
        int start_index = index * MATRIX_ROW_SIZE;
        int row_index = 0;
        
        for ( int i = start_index; i < start_index + MATRIX_ROW_SIZE; i++ ) {
            this.data[i] = row.get(row_index);
            row_index++;
        }
    }
    
    /**
     * This method returns an entire column from this Matrix.
     *
     * @param index The index of the column to retrieve.
     * @return Returns all of the values in the column specified at the 
     * provided index.
     */
    public Vector4f getColumn(int index) {
        int column_index = 0;
        Vector4f column = new Vector4f();
        
        for ( int i = index; i < MATRIX_SIZE; i += MATRIX_ROW_SIZE ) {
            column.set(column_index, this.data[i]);
            column_index++;
        }
        return column;
    }
    
    /**
     * This method returns an entire row from this Matrix.
     *
     * @param index The index of the row to retrieve.
     * @return Returns all of the values in the row specified at the 
     * provided index.
     */
    public Vector4f getRow(int index) {
        int start_index = index * MATRIX_ROW_SIZE;
        int row_index = 0;
        Vector4f row = new Vector4f();
        
        for ( int i = start_index; i < start_index + MATRIX_ROW_SIZE; i++ ) {
            row.set(row_index, this.data[i]);
            row_index++;
        }
        return row;
    }
    
    /**
     * This method multiplies the two provided Matrices.
     *
     * @param a The first Matrix.
     * @param b The second Matrix.
     * @return Returns the resulting Matrix.
     */
    public static Matrix4f multiply(Matrix4f a, Matrix4f b) {
        Matrix4f result = new Matrix4f();
        result.data[0] = (a.data[0] * b.data[0]) + (a.data[1] * b.data[4]) + 
                         (a.data[2] * b.data[8]) + (a.data[3] * b.data[12]);
        
        result.data[1] = (a.data[0] * b.data[1]) + (a.data[1] * b.data[5]) + 
                         (a.data[2] * b.data[9]) + (a.data[3] * b.data[13]);
        
        result.data[2] = (a.data[0] * b.data[2]) + (a.data[1] * b.data[6]) + 
                         (a.data[2] * b.data[10]) + (a.data[3] * b.data[14]);
        
        result.data[3] = (a.data[0] * b.data[3]) + (a.data[1] * b.data[7]) + 
                         (a.data[2] * b.data[11]) + (a.data[3] * b.data[15]);
        
        result.data[4] = (a.data[4] * b.data[0]) + (a.data[5] * b.data[4]) + 
                         (a.data[6] * b.data[8]) + (a.data[7] * b.data[12]);
        
        result.data[5] = (a.data[4] * b.data[1]) + (a.data[5] * b.data[5]) + 
                         (a.data[6] * b.data[9]) + (a.data[7] * b.data[13]);
        
        result.data[6] = (a.data[4] * b.data[2]) + (a.data[5] * b.data[6]) + 
                         (a.data[6] * b.data[10]) + (a.data[7] * b.data[14]);
        
        result.data[7] = (a.data[4] * b.data[3]) + (a.data[5] * b.data[7]) + 
                         (a.data[6] * b.data[11]) + (a.data[7] * b.data[15]);
        
        result.data[8] = (a.data[8] * b.data[0]) + (a.data[9] * b.data[4]) + 
                         (a.data[10] * b.data[8]) + (a.data[11] * b.data[12]);
        
        result.data[9] = (a.data[8] * b.data[1]) + (a.data[9] * b.data[5]) + 
                         (a.data[10] * b.data[9]) + (a.data[11] * b.data[13]);
        
        result.data[10] = (a.data[8] * b.data[2]) + (a.data[9] * b.data[6]) + 
                          (a.data[10] * b.data[10]) + (a.data[11] * b.data[14]);
        
        result.data[11] = (a.data[8] * b.data[3]) + (a.data[9] * b.data[7]) + 
                          (a.data[10] * b.data[11]) + (a.data[11] * b.data[15]);
        
        result.data[12] = (a.data[12] * b.data[0]) + (a.data[13] * b.data[4]) + 
                          (a.data[14] * b.data[8]) + (a.data[15] * b.data[12]);
        
        result.data[13] = (a.data[12] * b.data[1]) + (a.data[13] * b.data[5]) +
                          (a.data[14] * b.data[9]) + (a.data[15] * b.data[13]);
        
        result.data[14] = (a.data[12] * b.data[2]) + (a.data[13] * b.data[6]) +
                          (a.data[14] * b.data[10]) + (a.data[15] * b.data[14]);
        
        result.data[15] = (a.data[12] * b.data[3]) + (a.data[13] * b.data[7]) +
                          (a.data[14] * b.data[11]) + (a.data[15] * b.data[15]);
        return result;
    }
    
    /**
     * Returns the String representation of a Matrix.
     * The String representation follows the following format:<br />
     * [ value1  value2  value3  value4  ]<br />
     * [ value5  value6  value7  value8  ]<br />
     * [ value9  value10 value11 value12 ]<br />
     * [ value13 value14 value15 value16 ]<br />
     *
     * @return Returns the String representation of a Matrix.
     */
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("[\t");
        for ( int i = 0 ; i < MATRIX_SIZE; i++ ) {
            if ( i != 0 && i % MATRIX_ROW_SIZE == 0 )
                builder.append("]\n[\t" + this.data[i] + "\t");
            else
                builder.append(this.data[i] + "\t");
        }
        builder.append("]");
        return builder.toString();
    }
}